package multimedia.controller;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.management.modelmbean.InvalidTargetObjectTypeException;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTextField;

import magick.ColorspaceType;
import magick.MagickException;
import multimedia.model.Decoder;
import multimedia.model.ModifiedImage;
import multimedia.model.coding.HuffmanEncoder;
import multimedia.model.coding.LZW;
import multimedia.model.colorspace.ColorspaceInterface;
import multimedia.model.colorspace.ColorspaceUtil;
import multimedia.model.colorspace.RGB;
import multimedia.model.encoding.Predictor;
import multimedia.model.quantize.Quantizer;
import multimedia.util.ProjectConstants;
import multimedia.view.EncodingGui;

public class EncodingController
{
    private EncodingGui view;
    private ModifiedImage model;
    
    public EncodingController( EncodingGui view, ModifiedImage model )
    {
        this.view = view;
        this.model = model;
        
        // Add Listeners
        view.setGenerateFileButtonListener( new CodeFileButtonListener() );
        view.setDecodeFileButtonListener( new CodeFileButtonListener() );
        view.setDistortionButtonListener(new CodeFileButtonListener() );
        view.setBrowseButtonListener( new BrowseButtonListener() );
    }
    
    class CodeFileButtonListener implements ActionListener
    {

        @Override
        public void actionPerformed( ActionEvent e )
        {
            int selectedTab =  view.getCodingTabbedPane().getSelectedIndex();
            
            if( view.getPathTextField().getText().isEmpty() )
            {
                String message;
                
                if( selectedTab == 0 )
                    message = "generate";
                else if( selectedTab == 1 )
                    message = "decode";
                else
                    message = "calculate distortion of";
                
               JOptionPane.showMessageDialog(((JComponent)e.getSource()).getRootPane(), 
                                              "Please select a file before trying to " + message + " a binary file.",
                                              "Error: No Input File",
                                              JOptionPane.ERROR_MESSAGE);
            }
            else
            {
                try
                {
                    if( selectedTab == 0 )
                        encode(); 
                    else if( selectedTab == 1 )
                        decode();
                    else
                        calculateDistortion();
                    
                } catch ( FileNotFoundException e1 )
                {
                    e1.printStackTrace();
                } catch ( MagickException e2 )
                {
                    e2.printStackTrace();
                } catch ( InvalidTargetObjectTypeException e3 )
                {
                    JOptionPane.showMessageDialog( ((JComponent)e.getSource()).getRootPane(), 
                    "Choose a compatible file for the operation you want to perform." , "Invalid Filetype", JOptionPane.ERROR_MESSAGE);
                }
                
                System.out.print("Colorspace: ");
                System.out.println(view.getColorspaceButtonGroup().getSelection().getActionCommand() );
                
                System.out.print("Q: ");
                System.out.println(view.getQuantizationButtonGroup().getSelection().getActionCommand() );
                
                System.out.print("PC: ");
                System.out.println(view.getPredictiveCodingButtonGroup().getSelection().getActionCommand() );
                
                System.out.print("E: ");
                System.out.println(view.getEncodingButtonGroup().getSelection().getActionCommand() + "\n");
            }
        }

        private void calculateDistortion() throws MagickException
        {
            ModifiedImage originalImage = new ModifiedImage(view.getPathTextField().getText());
            ModifiedImage distortedImage = new ModifiedImage(view.getDistortedFileField().getText());
            
            RGB distortedValues = distortedImage.calculateDistortion(originalImage);
            view.getDistortionLabel().setText( distortedValues.r + ", " + distortedValues.g + ", " + distortedValues.b );
        }

        private void decode() throws FileNotFoundException, MagickException, InvalidTargetObjectTypeException
        {
            Decoder decoder = new Decoder(new File( view.getPathTextField().getText() ));
            model = decoder.parseBinaryFile();

            // Decoding Takes Part here //
            // TODO: Create an Encoder
            
            // Create a Predictor
            Predictor predictor = new Predictor(model, decoder.getPredictorType() );
            predictor.encodePixels(false);
            
            // Write out output //
            model.writeToBinaryFile( ProjectConstants.BINARY_DECODED + "decoded_" + model.getOriginalFilename() , model.getImagePixelHolder().toString());
            
            // Convert to RGB
            if( view.getPathTextField().getText().endsWith("YIQ") )
            {
                Vector<ColorspaceInterface> temp = model.getImagePixelHolder().getRgbVector();
                model.getImagePixelHolder().setRgbVector( ColorspaceUtil.ConvertFromYIQ(temp) );
//                model.writeToBinaryFile( ProjectConstants.BINARY_DECODED + "decoded_" + model.getOriginalFilename() + ".RGB");
            }
            model.writeBinaryToImage(model.getOriginalFilename() + ".png");
        }

        private void encode() throws FileNotFoundException, MagickException, InvalidTargetObjectTypeException
        {
            // Encoded Output String
            StringBuilder output = new StringBuilder();
            
            // Create a modified image object based on the what kind of file extension it has. This is just done for opening binary files
            if ( view.getPathTextField().getText().endsWith("YIQ") || view.getPathTextField().getText().endsWith("RGB") ) 
            {
                model = (new Decoder(new File(view.getPathTextField().getText()))).parseBinaryFile();
            }
            else
            {
            	model = new ModifiedImage(view.getPathTextField()
            			.getText());
            }
            
            String colorspace = view.getColorspaceButtonGroup().getSelection().getActionCommand();
            String predictiveCoding = view.getPredictiveCodingButtonGroup().getSelection().getActionCommand();
            String quantization = view.getQuantizationButtonGroup().getSelection().getActionCommand();
            String encoding = view.getEncodingButtonGroup().getSelection().getActionCommand();
            String header = "";
            
            // Set colorspace
            if( colorspace.equals("YIQ") )
            {
                model.setColorspace( ColorspaceType.YIQColorspace );
                model.getImagePixelHolder().setRgbVector( ColorspaceUtil.ConvertToYIQ( model.getImagePixelHolder().getRgbVector() ) );
            }
            else 
            {
                model.setColorspace( ColorspaceType.RGBColorspace );
            }
            
            // Set Quantization 
            header += quantization + " "; // Maybe here we can add w/e table we need for Quantization
            
            // Perform quantization
            if( Integer.parseInt(quantization) != 0 )
            {
                int n1 = Integer.parseInt(view.getQuantizationParameters()[0].getText() );
                int n2 = Integer.parseInt(view.getQuantizationParameters()[1].getText() );
                int n3 = Integer.parseInt(view.getQuantizationParameters()[2].getText() );
                float alpha = (float) Double.parseDouble( view.getQuantizationParameters()[3].getText());
                
                Quantizer quantizer = new Quantizer( model, model.getColorspace() );
                
                if( Integer.parseInt(quantization) == Quantizer.UNIFORM )
                    quantizer.uniformQuantization( n1 , n2 , n3 );
                else
                    quantizer.variableQuantization( n1, n2, n3, alpha );
            }
            
            
            // Set Predictive Coding
            header += predictiveCoding + " ";
            
            // Perform the predictive coding
            Predictor predictor = new Predictor(model, Integer.parseInt(predictiveCoding));
            predictor.encodePixels(true);
            
            
            // Set Encoding
            header += encoding;
            
            // Perform encoding
            if( Integer.parseInt(encoding) != 0 )
            {
                String[] stringRgbArrays = model.getImagePixelHolder().createComponentStrings();
                if( Integer.parseInt(encoding) == LZW.LZW )
                {
                    for ( String string : stringRgbArrays )
                    {
                        LZW encoder = new LZW( string );
                        output.append(encoder.encode());
                        output.append('\n');
                    }
                }
                else if( Integer.parseInt(encoding) == HuffmanEncoder.HUFF )
                {
                    for ( String string : stringRgbArrays )
                    {
                        // Create Table and Encode
                        HuffmanEncoder encoder = new HuffmanEncoder();
                        Hashtable<Character, String> table = new Hashtable<Character, String>();
                        ArrayList<Byte> binaryEncodedData = encoder.encode(string, table);

                        output.append(string.length() + " " + table.size() + "\n");
                        
                        Enumeration<Character> enumerator = table.keys();
                        while( enumerator.hasMoreElements() )
                        {
                            Object key = enumerator.nextElement();
                            output.append(  (Character)key + " " + (String)table.get(key) + "\n");
                        }
                        
                        for ( Byte byte1 : binaryEncodedData )
                        {
                            output.append( byte1 + " " );
                        }
                        output.append('\n');
                    }
                }
            }
            else
            {
                output.append(model.getImagePixelHolder().toString());
            }
            
            // Write binary file
            model.setBinaryHeader(header);
            model.writeToBinaryFile(ProjectConstants.BINARY_ENCODED + 
            						model.getOriginalFilename().substring( 0, model.getOriginalFilename().length() - 4 )  + "Encoded." + colorspace,
            						output.toString());
        }
    }
    
    class BrowseButtonListener implements ActionListener
    {

        @Override
        public void actionPerformed( ActionEvent e )
        {
            // Set current text field
            JTextField currentTextfield = null;
            if( e.getActionCommand().equals("browseButton") )
                currentTextfield = view.getPathTextField();
            else
                currentTextfield = view.getDistortedFileField();
            
            // Create a file browser 
            JFileChooser fileBrowser = new JFileChooser();
            
            if ( view.getCodingTabbedPane().getSelectedIndex() == 0 || view.getCodingTabbedPane().getSelectedIndex() == 2 )
            {
                fileBrowser.setCurrentDirectory(new File(ProjectConstants.IMAGE_DIR));
                if( e.getActionCommand().equals("Browse...") )
                    fileBrowser.setCurrentDirectory( new File(ProjectConstants.IMAGE_OUTDIR) );
            }
            else
                fileBrowser.setCurrentDirectory(new File(ProjectConstants.BINARY_ENCODED));
            // Start chooser
            int selectedOption = fileBrowser.showOpenDialog(((JComponent) e.getSource()).getRootPane());
            // Clear any previous entry
            currentTextfield.setText("");
            if ( selectedOption == JFileChooser.APPROVE_OPTION )
            {
                currentTextfield.setText(fileBrowser.getSelectedFile().getAbsolutePath());
            }
        }
        
    }
}
